#include "PreCompile.hpp"
#include "AsyncRingBufferTests.hpp"
#include "../AsyncRingBuffer.hpp"

namespace BuildNG
{
  static gint num_instances = 0;
  class InstanceTracker
  {
  public:
    InstanceTracker(int thread_index,int instance_index)
    {
      this->thread_index = thread_index;
      this->instance_index = instance_index;

      g_atomic_int_inc(&num_instances);
    }

    InstanceTracker(const InstanceTracker &src)
    {
      g_atomic_int_inc(&num_instances);

      thread_index = src.thread_index;
      instance_index = src.instance_index;
    }

    ~InstanceTracker()
    {
      g_atomic_int_dec_and_test(&num_instances);
    }

    int thread_index;
    int instance_index;
  };

  void AsyncRingBufferTests::test_sync_aligned_size()
  {
    num_instances = 0;
    assert(num_instances == 0);

    int index = 1;
    int pop_index = 1;

    AsyncRingBuffer ring_buffer(4 * (sizeof(AsyncRingBuffer::Header) + sizeof(InstanceTracker)));
    for(int i = 0;i < 2;i++)
    {
      bool push_res = ring_buffer.push(InstanceTracker(0,index++));
      assert(push_res);
    }

    for(int i = 0;i < 4;i++)
    {
      InstanceTracker *popped = (InstanceTracker*)ring_buffer.pop_acquire();
      assert(popped);
      assert(popped->thread_index == 0);
      assert(popped->instance_index == pop_index);
      pop_index++;
      ring_buffer.pop_release(popped);

      bool push_res = ring_buffer.push(InstanceTracker(0,index++));
      assert(push_res);
    }

    for(int i = 0;i < 2;i++)
    {
      bool push_res = ring_buffer.push(InstanceTracker(0,index++));
      assert(push_res == (i == 0));
    }

    for(int i = 0;i < 3;i++)
    {
      InstanceTracker *popped = (InstanceTracker*)ring_buffer.pop_acquire();
      assert(popped);
      assert(popped->thread_index == 0);
      assert(popped->instance_index == pop_index);
      pop_index++;
      ring_buffer.pop_release(popped);
    }

    assert(num_instances == 0);
  }

  void AsyncRingBufferTests::test_sync_unaligned_size()
  {
    num_instances = 0;
    assert(num_instances == 0);

    int index = 1;
    int pop_index = 1;

    AsyncRingBuffer ring_buffer(4 * (sizeof(AsyncRingBuffer::Header) + sizeof(InstanceTracker)) + 4);
    for(int i = 0;i < 2;i++)
    {
      bool push_res = ring_buffer.push(InstanceTracker(0,index++));
      assert(push_res);
    }

    for(int i = 0;i < 4;i++)
    {
      InstanceTracker *popped = (InstanceTracker*)ring_buffer.pop_acquire();
      assert(popped);
      assert(popped->thread_index == 0);
      assert(popped->instance_index == pop_index);
      pop_index++;
      ring_buffer.pop_release(popped);

      bool push_res = ring_buffer.push(InstanceTracker(0,index++));
      assert(push_res);
    }

    for(int i = 0;i < 2;i++)
    {
      bool push_res = ring_buffer.push(InstanceTracker(0,index++));
      assert(push_res == (i == 0));
    }

    for(int i = 0;i < 3;i++)
    {
      InstanceTracker *popped = (InstanceTracker*)ring_buffer.pop_acquire();
      assert(popped);
      assert(popped->thread_index == 0);
      assert(popped->instance_index == pop_index);
      pop_index++;
      ring_buffer.pop_release(popped);
    }

    assert(num_instances == 0);
  }

  void AsyncRingBufferTests::test_dtor_non_empty_buffer()
  {
    num_instances = 0;

    {
      AsyncRingBuffer ring_buffer(4 * (sizeof(AsyncRingBuffer::Header) + sizeof(InstanceTracker)));
      ring_buffer.push(InstanceTracker(0,0));
      ring_buffer.push(InstanceTracker(0,1));
    }

    assert(num_instances == 0);
  }

  void AsyncRingBufferTests::test_wrap_around1()
  {
    num_instances = 0;

    AsyncRingBuffer ring_buffer(4 * (sizeof(AsyncRingBuffer::Header) + sizeof(InstanceTracker)));
    for(int i = 0;i < 8;i++)
    {
      ring_buffer.push(InstanceTracker(0,i));
    }

    int popped_index = 0;
    for(int i = 0;i < 8;i++)
    {
      InstanceTracker *popped = (InstanceTracker*)ring_buffer.pop_acquire();
      if(popped)
      {
        assert(popped->instance_index == popped_index);
        popped_index++;
        ring_buffer.pop_release(popped);
      }
    }

    assert(num_instances == 0);
  }

  void AsyncRingBufferTests::test_wrap_around2()
  {
    num_instances = 0;

    int pushed_index = 0;

    AsyncRingBuffer ring_buffer(4 * (sizeof(AsyncRingBuffer::Header) + sizeof(InstanceTracker)));
    for(int i = 0;i < 4;i++)
    {
      if(ring_buffer.push(InstanceTracker(0,pushed_index)))
        pushed_index++;
    }

    int popped_index = 0;

    {
      InstanceTracker *popped = (InstanceTracker*)ring_buffer.pop_acquire();
      if(popped)
      {
        assert(popped->instance_index == popped_index);
        popped_index++;
        ring_buffer.pop_release(popped);
      }
    }

    for(int i = 0;i < 4;i++)
    {
      if(ring_buffer.push(InstanceTracker(0,pushed_index)))
        pushed_index++;
    }

    for(int i = 0;i < 8;i++)
    {
      InstanceTracker *popped = (InstanceTracker*)ring_buffer.pop_acquire();
      if(popped)
      {
        assert(popped->instance_index == popped_index);
        popped_index++;
        ring_buffer.pop_release(popped);
      }
    }

    assert(num_instances == 0);
  }

  void AsyncRingBufferTests::test_pop_all()
  {
    num_instances = 0;

    AsyncRingBuffer ring_buffer(4 * (sizeof(AsyncRingBuffer::Header) + sizeof(InstanceTracker)));

    for(int i = 0;i < 5;i++)
    {
      int num_popped = 0;
      for(int j = 0;j < i;j++)
      {
        if(ring_buffer.push(InstanceTracker(0,num_popped)))
          num_popped++;
      }

      for(int j = 0;j < num_popped;j++)
      {
        InstanceTracker *popped = (InstanceTracker*)ring_buffer.pop_acquire();
        assert(popped);
        assert(popped->instance_index == j);
        ring_buffer.pop_release(popped);
      }
    }

    assert(num_instances == 0);
  }

  struct ThreadParams
  {
    AsyncRingBuffer *ring_buffer;
    int thread_index;

    int pop_index;
  };

  static gpointer test_multiple_pushing_thread(gpointer data)
  {
    ThreadParams *thread_params = (ThreadParams*)data;
    AsyncRingBuffer *ring_buffer = thread_params->ring_buffer;

    int i = 0;
    while(i < 32)
    {
      if(ring_buffer->push(InstanceTracker(thread_params->thread_index,i)))
        i++;
    }

    return NULL;
  }

  void AsyncRingBufferTests::test_multiple_pushing_threads()
  {
    AsyncRingBuffer ring_buffer(16 * (sizeof(AsyncRingBuffer::Header) + sizeof(InstanceTracker)));
    ThreadParams thread_params[4];

    GThread *threads[4];
    for(int i = 0;i < 4;i++)
    {
      thread_params[i].ring_buffer = &ring_buffer;
      thread_params[i].thread_index = i;
      thread_params[i].pop_index = 0;
      threads[i] = g_thread_create(test_multiple_pushing_thread,&thread_params[i],true,0);
    }

    bool all_done;
    do
    {
      InstanceTracker *popped = (InstanceTracker*)ring_buffer.pop_acquire();
      if(popped)
      {
        assert(popped->thread_index >= 0 && popped->thread_index < 4);
        assert(popped->instance_index == thread_params[popped->thread_index].pop_index);

        thread_params[popped->thread_index].pop_index++;

        ring_buffer.pop_release(popped);
      }

      all_done = true;
      for(int i = 0;i < 4;i++)
        if(thread_params[i].pop_index != 32)
          all_done = false;
    }
    while(!all_done);

    for(int i = 0;i < 4;i++)
    {
      g_thread_join(threads[i]);
    }
  }
}
